package org.hackystat.sensor.ant.dependencyfinder;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.tools.ant.BuildException;
import org.hackystat.sensor.ant.dependencyfinder.jaxb.Dependencies;
import org.hackystat.sensor.ant.dependencyfinder.jaxb.Package;
import org.hackystat.sensor.ant.dependencyfinder.jaxb.Class;
import org.hackystat.sensor.ant.task.HackystatSensorTask;
import org.hackystat.sensor.ant.util.JavaClass2FilePathMapper;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * Implements an Ant task that parses the XML files generated by DependencyFinder. 
 * The Ant Task sends the Coupling data to a Hackystat server.
 * 
 * @author Philip Johnson
 */
public class DependencyFinderSensor extends HackystatSensorTask {

  /** The name of this tool. */
  private static String tool = "DependencyFinder";

  /** Initialize a new instance of this sensor. */
  public DependencyFinderSensor() {
    super(tool);
  }

  /**
   * Initialize a new instance of this sensor for testing purposes.
   * 
   * @param host The SensorBase host URL.
   * @param email The SensorBase email to use.
   * @param password The SensorBase password to use.
   */
  public DependencyFinderSensor(String host, String email, String password) {
    super(host, email, password, tool);
  }

  /**
   * Parses the tool's XML file and sends the resulting data to the SensorBase server.
   * 
   * @throws BuildException If there is an error.
   */
  @Override
  public void executeInternal() throws BuildException {
    this.setupSensorShell();
    int numberOfEntries = 0;
    Date startTime = new Date();
    for (File dataFile : getDataFiles()) {
      try {
        verboseInfo("Processing DependencyFinder file: " + dataFile);
        numberOfEntries += this.processDependencyFinderXmlFile(dataFile);
      }
      catch (Exception e) {
        signalError("Failure processing: " + dataFile, e);
      }
    }
    // We've collected the data, now send it. 
    this.sendAndQuit();
    summaryInfo(startTime, "Coupling", numberOfEntries);
  }

  /**
   * Processes a single DependencyFinder XML data file, generating sensor data.
   * 
   * @param xmlFile The file containing the DependencyFinder data.
   * @return The number of Coupling instances generated.
   * @throws BuildException If problems occur.
   */
  int processDependencyFinderXmlFile(File xmlFile) throws BuildException {
    // The start time for all entries will be approximated by the XML file's last mod time.
    // Use the TstampSet to make it unique.
    long startTime = xmlFile.lastModified();
    int count = 0;
    try {
      JAXBContext context = JAXBContext
      .newInstance(org.hackystat.sensor.ant.dependencyfinder.jaxb.ObjectFactory.class);
      Unmarshaller unmarshaller = context.createUnmarshaller();

      // DependencyFinder XML report.
      Dependencies dependencies = (Dependencies) unmarshaller.unmarshal(xmlFile);
      // Construct a mapper from class names to their file path.
      JavaClass2FilePathMapper mapper = new JavaClass2FilePathMapper(getSourceFiles());
      List<Package> packages = new ArrayList<Package>();
      if (dependencies.getPackage() != null) { 
        packages = dependencies.getPackage();
      }

      for (Package packageElement : packages) {
        List<Class> classList = new ArrayList<Class>();
        if (packageElement.getClazz() != null) {
          classList = packageElement.getClazz();
        }
        for (Class classElement : classList) {
          String resource = mapper.getFilePath(classElement.getName());

          if (resource != null) {
            long tstamp = this.tstampSet.getUniqueTstamp(startTime);
            XMLGregorianCalendar tstampXml = Tstamp.makeTimestamp(tstamp);
            XMLGregorianCalendar runtimeXml = Tstamp.makeTimestamp(this.runtime);
            // Create the sensor data instance key/value map.
            Map<String, String> keyValMap = new HashMap<String, String>();
            // Required for all sensor data
            keyValMap.put("Tool", tool);
            keyValMap.put("SensorDataType", "Coupling");
            keyValMap.put("Runtime", runtimeXml.toString());
            keyValMap.put("Timestamp", tstampXml.toString());
            keyValMap.put("Resource", resource);
            // Expected for "Coupling" sensor data. 
            keyValMap.put("Type", "class");
            keyValMap.put("Afferent", String.valueOf(getAfferent(classElement))); 
            keyValMap.put("Efferent", String.valueOf(getEfferent(classElement)));
            // add data to sensorshell
            this.sensorShell.add(keyValMap);
            count++;
          }
        }
      }
    }
    catch (Throwable e) {
      throw new BuildException(errMsgPrefix + "Failure: " + e.getMessage(), e);
    }
    return count;
  }

  /**
   * Gets the Afferent (inbound) number of couplings.
   * @param classElement The class element to count.
   * @return The integer number of afferent links.
   */
  private int getAfferent(Class classElement) {
    return (classElement.getInbound() == null) ? 0 : classElement.getInbound().size();
  }
  
  /**
   * Gets the Efferent (outbound) number of couplings.
   * @param classElement The class element to count.
   * @return The integer number of efferent links.
   */
  private int getEfferent(Class classElement) {
    return (classElement.getOutbound() == null) ? 0 : classElement.getOutbound().size();
  }
}
